#pragma once

#include <QObject>
#include <QHostAddress>
#include <QUdpSocket>
#include <QTimer>
#include <QByteArray>

class DiscoverClient : public QObject
{
	Q_OBJECT

signals:
	void serverFound(QHostAddress, quint16);
	void timeout();
	void log(const QString&);

public:
	explicit DiscoverClient(QObject* parent = nullptr):
		QObject(parent),
		m_socket(new QUdpSocket(this)),
		broadcast_timer(new QTimer(this)),
		broadcast_counter(0),
		is_searching(false)
	{
		connect(broadcast_timer, &QTimer::timeout, this, &DiscoverClient::sendBroadcast);
	}

	~DiscoverClient() = default;

	void start()
	{
		if (!is_searching)
		{
			m_socket->bind(QHostAddress::AnyIPv4, 11411);
			connect(m_socket, &QUdpSocket::readyRead, this, &DiscoverClient::readyRead);

			broadcast_timer->start(1000);
			is_searching = true;
		}
	}

	void stop()
	{
		disconnect(m_socket);
		m_socket->close();

		broadcast_timer->stop();
		is_searching = false;
	}

private:
	void processPacket(const QString& packet, const QHostAddress& host, quint16 port)
	{
		auto chunks = packet.split("\n");

		if (chunks.size() >= 3)
		{
			auto header  = chunks[0];

			if (header == "DISCOVER_RESPONSE")
			{
				auto address = QHostAddress(chunks[1]);
				auto port = chunks[2].toInt();

				QString msg;
				msg.append("QGBoy server @ ")
					.append(address.toString())
					.append(":")
					.append(QString::number(port));

				emit serverFound(address, port);
				emit log(msg);
			}
		}
	}

public slots:
	void readyRead()
	{
		// process recieve packets

		while (m_socket->hasPendingDatagrams())
		{
			QByteArray buffer;
			buffer.resize(m_socket->pendingDatagramSize());

			QHostAddress sender_addr;
			quint16      sender_port;

			m_socket->readDatagram(buffer.data(), buffer.size(), &sender_addr, &sender_port);

			processPacket(QString::fromStdString(buffer.toStdString()), sender_addr, sender_port);
		}
	}

	void sendBroadcast()
	{
		emit log("Requesting discovery...");

		QByteArray datagram;
		datagram.append("DISCOVER_REQUEST");

		m_socket->writeDatagram(datagram, QHostAddress::Broadcast, 11411);

		broadcast_counter++;

		if (broadcast_counter == 5)
		{
			broadcast_counter = 0;

			emit timeout();
		}
	}

private:
	QUdpSocket* m_socket{ nullptr };
	QTimer* broadcast_timer{ nullptr };
	int broadcast_counter;
	bool is_searching;
};
